import { _decorator, Component, instantiate, Node, Prefab, SpriteFrame } from 'cc'
import { ApiMsgEnum, EntityTypeEnum, IClientInput, IMsgClientSync, IMsgServerSync, InputTypeEnum } from '../Common'
import { ActorManager } from '../Entity/Actor/ActorManager'
import { BulletManager } from '../Entity/Bullet/BulletManager'
import { EventEnum, PrefabPathEnum, TexturePathEnum } from '../Enum'
import DataManager from '../Global/DataManager'
import EventManager from '../Global/EventManager'
import { NetworkManager } from '../Global/NetworkManager'
import { ObjectPoolManager } from '../Global/ObjectPoolManager'
import { ResourceManager } from '../Global/ResourceManager'
import { JoyStickManager } from '../UI/JoyStickManager'
import { deepClone } from '../Utils'
const { ccclass } = _decorator

@ccclass('BattleManager')
export class BattleManager extends Component {
  private stage: Node
  private ui: Node
  private shouldUpdate = false
  private pendingMsg: IMsgClientSync[] = []

  async start() {
    this.clearGame()
    await Promise.all([this.connectServer(), this.loadRes()])

    this.initGame()
  }

  initGame() {
    this.ui = this.node.getChildByName('UI')
    DataManager.Instance.jm = this.ui.getComponentInChildren(JoyStickManager)
    this.initMap()
    this.shouldUpdate = true
    EventManager.Instance.on(EventEnum.ClientSync, this.handleClientSync, this)
    NetworkManager.Instance.listenMsg(ApiMsgEnum.MsgServerSync, this.handleServerSync, this)
  }

  clearGame() {
    EventManager.Instance.off(EventEnum.ClientSync, this.handleClientSync, this)
    NetworkManager.Instance.unListenMsg(ApiMsgEnum.MsgServerSync, this.handleServerSync, this)
    DataManager.Instance.stage = this.stage = this.node.getChildByName('Stage')
    this.stage.destroyAllChildren()
  }

  async connectServer() {
    if (!(await NetworkManager.Instance.connect().catch(() => false))) {
      await new Promise(resolve => setTimeout(resolve, 1000))
      await this.connectServer()
    }
  }

  async loadRes() {
    const list = []
    // 加载预制体
    for (const type in PrefabPathEnum) {
      const p = ResourceManager.Instance.loadRes(PrefabPathEnum[type], Prefab).then(prefab => {
        DataManager.Instance.prefabMap.set(type, prefab)
      })
      list.push(p)
    }

    // 加载贴图
    for (const type in TexturePathEnum) {
      const p = ResourceManager.Instance.loadDir(TexturePathEnum[type], SpriteFrame).then(spriteFrames => {
        DataManager.Instance.textureMap.set(type, spriteFrames)
      })
      list.push(p)
    }
    await Promise.all(list)
  }

  initMap() {
    const map = DataManager.Instance.prefabMap.get(EntityTypeEnum.Map)
    const mapNode = instantiate(map)
    mapNode.setParent(this.stage)
  }

  update(dt: number) {
    if (!this.shouldUpdate) return
    this.render()
    this.tick(dt)
  }

  render() {
    this.renderActor()
    this.renderBullet()
  }

  renderActor() {
    const { actors } = DataManager.Instance.state
    for (const actor of actors) {
      const { id, type } = actor
      let am = DataManager.Instance.actorMap.get(id)
      if (!am) {
        const prefab = DataManager.Instance.prefabMap.get(type)
        const actorNode = instantiate(prefab)
        actorNode.setParent(this.stage)
        am = actorNode.addComponent(ActorManager)
        DataManager.Instance.actorMap.set(id, am)
        am.init(actor)
      } else {
        am.render(actor)
      }
    }
  }

  renderBullet() {
    const { bullets } = DataManager.Instance.state
    for (const bullet of bullets) {
      const { id, type } = bullet
      let bm = DataManager.Instance.bulletMap.get(id)
      if (!bm) {
        const bulletNode = ObjectPoolManager.Instance.get(type)
        bm = bulletNode.getComponent(BulletManager) || bulletNode.addComponent(BulletManager)
        DataManager.Instance.bulletMap.set(id, bm)
        bm.init(bullet)
      } else {
        bm.render(bullet)
      }
    }
  }

  tick(dt: number) {
    this.tickActor(dt)
  }

  tickActor(dt: number) {
    const { actors } = DataManager.Instance.state
    for (const actor of actors) {
      const am = DataManager.Instance.actorMap.get(actor.id)
      am.tick(dt)
    }
  }

  handleClientSync(input: IClientInput) {
    const msg = {
      input,
      frameId: DataManager.Instance.frameId++
    }
    NetworkManager.Instance.sendMsg(ApiMsgEnum.MsgClientSync, msg)
    // 客户端应用输入,作为预测输入
    if (input.type === InputTypeEnum.ActorMove) {
      DataManager.Instance.applyInput(input)
      this.pendingMsg.push(msg)
    }
  }

  handleServerSync({ inputs, lastFrameId }: IMsgServerSync) {
    DataManager.Instance.state = DataManager.Instance.lastState
    for (const input of inputs) {
      DataManager.Instance.applyInput(input)
    }
    // 更新lastState
    DataManager.Instance.lastState = deepClone(DataManager.Instance.state)
    // 回滚
    this.pendingMsg = this.pendingMsg.filter(msg => msg.frameId > lastFrameId)
    for (const msg of this.pendingMsg) {
      DataManager.Instance.applyInput(msg.input)
    }
  }
}
